//********************************************************************************************************
// Product Name: MapWindow.dll Alpha
// Description:  The core libraries for the MapWindow 6.0 project.
//
//********************************************************************************************************
// The contents of this file are subject to the Mozilla Public License Version 1.1 (the "License"); 
// you may not use this file except in compliance with the License. You may obtain a copy of the License at 
// http://www.mozilla.org/MPL/ 
//
// Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF 
// ANY KIND, either expressed or implied. See the License for the specificlanguage governing rights and 
// limitations under the License. 
//
// The Original Code is MapWindow.dll for the MapWindow 6.0 project
//
// The Initial Developer of this Original Code is Ted Dunsford. Created in August, 2007.
// 
// Contributor(s): (Open source contributors should list themselves and their modifications here). 
// Modified to do 3D in January 2008 by Ted Dunsford
//********************************************************************************************************
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using MapWindow.Data;
using MapWindow.Drawing;
using MapWindow.Geometries;
using MapWindow.Main;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;


namespace MapWindow.DirectX
{
    
    /// <summary>
    /// This is a specialized FeatureLayer that specifically handles point drawing
    /// </summary>
    public class PointLayerDX: PointLayer, IPointLayerDX
    {
        
        
        #region Private Variables


        //private Microsoft.DirectX.Direct3D.VertexBuffer _vertexBuffer;
        private VertexCollection _vertices;
        private IndexCollection _indices;
        private TextureComponent _texture;
        private bool _isTextured;
        int _pointCount;
        

        #endregion



        #region Constructors

        /// <summary>
        /// Creates a new instance of a PointLayer without sending any status messages
        /// </summary>
        /// <param name="featureSet">The IFeatureLayer of data values to turn into a graphical PointLayer</param>
        public PointLayerDX(IFeatureSet featureSet)
            : this(featureSet, null)
        {
            // this simply handles the default case where no status messages are requested
            Symbolizer.ScaleMode = ScaleModes.Simple;
        }

        /// <summary>
        /// Creates a new instance of a PointLayer for storing and drawing points
        /// </summary>
        /// <param name="featureSet">Any implentation of an IFeatureLayer</param>
        /// <param name="progressHandler">A valid implementation of the IProgressHandler interface.</param>
        public PointLayerDX(IFeatureSet featureSet, IProgressHandler progressHandler)
            : base(featureSet, progressHandler)
        {
            Symbolizer.ScaleMode = ScaleModes.Simple;
           
        }

        /// <summary>
        /// Creates a new instance of a PointLayer for storing and drawing points
        /// </summary>
        /// <param name="featureSet">Any implentation of an IFeatureLayer</param>
        /// <param name="progressHandler">A valid implementation of the IProgressHandler interface.</param>
        /// <param name="container">An ICollection of ILayer to contain this layer</param>
        public PointLayerDX(IFeatureSet featureSet, ICollection<ILayer> container, IProgressHandler progressHandler)
            : base(featureSet, container, progressHandler)
        {
            Symbolizer.ScaleMode = ScaleModes.Simple;
        }

        /// <summary>
        /// Copies values after properties were changed
        /// </summary>
        /// <param name="editCopy"></param>
        protected override void OnCopyProperties(object editCopy)
        {
            base.OnCopyProperties(editCopy);
            IsInitialized = false;
        }

        #endregion

        #region Methods

      
      
       


        /// <summary>
        /// This is the internal drawing method that follows the standard checks.  Drawing is evoked
        /// through the Draw3D command, but internally implemented in the DrawPart method.  After
        /// the initial checks, the Draw3D method will cycle the part integer from 0 to NumParts -1.
        /// </summary>
        /// <param name="args">A DrawArgsDX class with the device and camera.</param>
        protected void OnDraw(DrawArgsDX args)
        {
            IPointSymbolizer pointSymbolizer = Symbolizer;

            if (DataSet.Features.Count <= 0) return;
            args.Device.RenderState.ZBufferEnable = false;
                
            if (_texture != null && pointSymbolizer.ScaleMode != ScaleModes.Simple) _texture.BeforeDrawing(args.Device);
            if (pointSymbolizer.ScaleMode != ScaleModes.Simple) _indices.BeforeDrawing(args.Device);
            _vertices.BeforeDrawing(args.Device);

            if(pointSymbolizer.ScaleMode != ScaleModes.Simple)
            {
                args.Device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, _vertices.Count, 0, _indices.Count / 3);
            }
            else
            {
                //args.Device.SetTexture(0, null);
                args.Device.RenderState.PointSize = (float)pointSymbolizer.GetSize().Width;
                args.Device.DrawPrimitives(PrimitiveType.PointList, 0, _vertices.Count);
            }


            if (_texture != null && pointSymbolizer.ScaleMode != ScaleModes.Simple) _texture.AfterDrawing(args.Device);
            if (pointSymbolizer.ScaleMode != ScaleModes.Simple) _indices.AfterDrawing(args.Device);
            _vertices.AfterDrawing(args.Device);
        }

        #endregion


    

        #region Private Functions

        

        /// <summary>
        /// 
        /// </summary>
        public void Initialize(DrawArgsDX args)
        {

            IPointSymbolizer pointSymbolizer = Symbolizer;
            GC.Collect();
            PreviousScale = GetScale(args);
            Vector geoScale = args.Camera.DrawBox.GetScale();

            System.Diagnostics.Debug.WriteLine("DrawWindow Width:" + (args.Camera.DrawBox.MaxX - args.Camera.DrawBox.MinX));
            System.Diagnostics.Debug.WriteLine("SCALE: " + PreviousScale);
            IFeatureList features = DataSet.Features;

            _pointCount = 0;
            foreach (IFeature f in features)
            {
                // multi-point features should add their total number of points to the count.
                _pointCount += f.BasicGeometry.NumGeometries;
            }

            
            if (_texture != null) _texture.Dispose();
            if (_vertices != null) _vertices.Dispose();
               
   
            ProgressMeter pm = new ProgressMeter(ProgressHandler, "Creating vertex buffer", _pointCount);
            if (_pointCount < 1000000) pm.StepPercent = 5;
            if (_pointCount < 500000) pm.StepPercent = 10;
            if (_pointCount < 100000) pm.StepPercent = 50;
            int iFeature = 0;
            if (pointSymbolizer.ScaleMode == ScaleModes.Simple)
            {
                _vertices = new PositionColoredVC(pointSymbolizer.GetFillColor());
                _vertices.DrawBox = args.Camera.DrawBox;
                // Randomly select the position of this vertex..  Keep it within a range of 5.
                foreach (IFeature f in features)
                {


                    // Multipoint and point?

                    for (int i = 0; i < f.BasicGeometry.NumGeometries; i++)
                    {
                        IBasicGeometry bg = f.BasicGeometry.GetBasicGeometryN(i);
                        for (int iCoord = 0; iCoord < bg.NumPoints; iCoord++)
                        {
                            Coordinate position = bg.Coordinates[iCoord];
                            _vertices.AddCoordinate(position);
                        }
                    }
                    // Increment the progress meter and send messages only for integer changes of the percent.
                    pm.CurrentValue = iFeature++;

                }
                pm.Reset();
                IsInitialized = true;
                return;
            }
            if (_isTextured)
            {
                // Create the texture from the point symbol

                _texture = new TextureComponent();
                _vertices = new PositionTexturedVC();
                _vertices.DrawBox = args.Camera.DrawBox;
                Size2D symbolSize = pointSymbolizer.GetSize();
                Bitmap bmp = new Bitmap((int)(symbolSize.Width+1), (int)(symbolSize.Height + 1));
                Graphics g = Graphics.FromImage(bmp);
                pointSymbolizer.Draw(g, new Rectangle(0, 0, bmp.Width, bmp.Height));
                g.Dispose();
                _texture.TextureDX = new Texture(args.Device, bmp, Usage.AutoGenerateMipMap, Pool.Managed);
                bmp.Dispose();
         
                // Randomly select the position of this vertex..  Keep it within a range of 5.
                foreach (IFeature f in features)
                {


                    // Multipoint and point
                    Size2D geoSize = pointSymbolizer.GetSize();
                    if (pointSymbolizer.ScaleMode == ScaleModes.Symbolic)
                    {
                        geoSize.Width = geoSize.Width * PreviousScale * geoScale.X;
                        geoSize.Height = geoSize.Height * PreviousScale * geoScale.Y;
                    }
                    for (int i = 0; i < f.BasicGeometry.NumGeometries; i++)
                    {
                        IBasicGeometry bg = f.BasicGeometry.GetBasicGeometryN(i);
                        Coordinate position;
                        for (int iCoord = 0; iCoord < bg.NumPoints; iCoord++)
                        {
                            position = bg.Coordinates[iCoord];
                            if (position == null) continue;
                            Coordinate p1 = new Coordinate(position.X - geoSize.Width / 2, position.Y + geoSize.Height / 2);
                            Coordinate p2 = new Coordinate(position.X + geoSize.Width / 2, position.Y + geoSize.Height / 2);
                            Coordinate p3 = new Coordinate(position.X - geoSize.Width / 2, position.Y - geoSize.Height / 2);
                            Coordinate p4 = new Coordinate(position.X + geoSize.Width / 2, position.Y - geoSize.Height / 2);
                            _vertices.AddCoordinate(p1);
                            _vertices.AddCoordinate(p2);
                            _vertices.AddCoordinate(p3);
                            _vertices.AddCoordinate(p4);
                        }
                    }
                    // Increment the progress meter and send messages only for integer changes of the percent.
                    pm.CurrentValue = iFeature++;
                }
                
              
            }
            else
            {
                _vertices = new PositionColoredVC(pointSymbolizer.GetFillColor());
                _vertices.DrawBox = args.Camera.DrawBox;

                Size2D geoSize = pointSymbolizer.GetSize();
                if (pointSymbolizer.ScaleMode == ScaleModes.Symbolic)
                {
                    geoSize.Width = geoSize.Width * PreviousScale * geoScale.X;
                    geoSize.Height = geoSize.Height * PreviousScale * geoScale.Y;
                }
              
                // Randomly select the position of this vertex..  Keep it within a range of 5.
                foreach (IFeature f in features)
                {


                    // Multipoint and point?

                    for (int i = 0; i < f.BasicGeometry.NumGeometries; i++)
                    {
                        IBasicGeometry bg = f.BasicGeometry.GetBasicGeometryN(i);
                        
                        for (int iCoord = 0; iCoord < bg.NumPoints; iCoord++)
                        {
                            Coordinate position = bg.Coordinates[iCoord];
                            if(position == null)continue;
                            Coordinate p1 = new Coordinate(position.X - geoSize.Width / 2,position.Y + geoSize.Height / 2);
                            Coordinate p2 = new Coordinate(position.X + geoSize.Width / 2, position.Y + geoSize.Height / 2);
                            Coordinate p3 = new Coordinate(position.X - geoSize.Width / 2, position.Y - geoSize.Height / 2);
                            Coordinate p4 = new Coordinate(position.X + geoSize.Width / 2, position.Y - geoSize.Height / 2);
                            _vertices.AddCoordinate(p1);
                            _vertices.AddCoordinate(p2);
                            _vertices.AddCoordinate(p3);
                            _vertices.AddCoordinate(p4);
                        }
                    }
                    // Increment the progress meter and send messages only for integer changes of the percent.
                    pm.CurrentValue = iFeature++;

                }
                

            }

            pm.Reset();

            // Actually call set data for the unmanaged buffer
            _indices = new IndexCollection(_pointCount);

            IsInitialized = true;
        }

       


        /// <summary>
        /// Allows the object to dispose the unmannaged memory components.
        /// </summary>
        protected override void Dispose(bool disposing)
        {
            if(_texture != null)_texture.Dispose();
            if(_indices!=null)_indices.Dispose();
            if(_vertices != null) _vertices.Dispose();
            base.Dispose(disposing);
        }
        

        #endregion




        #region Static Methods

        /// <summary>
        /// Attempts to create a new PointLayer using the specified file.  If the filetype is not
        /// does not generate a point layer, an exception will be thrown.
        /// </summary>
        /// <param name="filename">A string filename to create a point layer for.</param>
        /// <param name="progressHandler">Any valid implementation of IProgressHandler for receiving progress messages</param>
        /// <returns>A PointLayer created from the specified filename.</returns>
        public new static IPointLayer OpenFile(string filename, IProgressHandler progressHandler)
        {
            ILayer fl = MapWindow.Components.LayerManager.DefaultLayerManager.OpenLayer(filename, progressHandler);
            return fl as PointLayer;
           
        }

        /// <summary>
        /// Attempts to create a new PointLayer using the specified file.  If the filetype is not
        /// does not generate a point layer, an exception will be thrown.
        /// </summary>
        /// <param name="filename">A string filename to create a point layer for.</param>
        /// <returns>A PointLayer created from the specified filename.</returns>
        public new static IPointLayer OpenFile(string filename)
        {
            IFeatureLayer fl = MapWindow.Components.LayerManager.DefaultLayerManager.OpenVectorLayer(filename);
            return fl as PointLayer;

        }




        #endregion

        // The following code region represents methods that are common between Point, Line, and Polygon layers.
        #region Common Rendering Content

        #region Events

        /// <summary>
        /// Occurs immediately before drawing, allowing it to be cancelled
        /// </summary>
        public event EventHandler<DrawVerifyArgsDX> BeforeDrawing;

        /// <summary>
        /// Occurs after drawing, indicating if an exception or cancellation occured
        /// </summary>
        public event EventHandler<DrawCompletedArgsDX> DrawingCompleted;

        /// <summary>
        /// Occurs when the data has been loaded into the model after the first drawing method
        /// </summary>
        public event EventHandler<DrawArgsDX> Inititialized;


        #endregion

        #region Private Variables

        private int _numParts = 1;
        private int _numStages = 1;
        private DrawBox _drawBox;
        private float _previousScale;
        private double _previousZ;
      
        #endregion

        #region Methods

      
        

        /// <summary>
        /// Causes the layer or object to draw itself to the map.  This may initiate
        /// one-time loading of buffers, but should not do heavy calculations every
        /// cycle.
        /// </summary>
        /// <param name="args">A Drawing Args structure.  This is used so that this method
        /// can also be called asynchronously</param>
        public virtual void Draw3D(DrawArgsDX args)
        {
           // try
           // {
                if (IsVisible == false)
                {
                    OnDrawingCompleted(new DrawCompletedArgsDX(args, false, null));
                    return;
                }
                if (OnBeforeDrawing(new DrawVerifyArgsDX(args, false)))
                {
                    OnDrawingCompleted(new DrawCompletedArgsDX(args, true, null));
                    return;
                }

                OnDrawSetup(new DrawSetupArgs(args.Camera));
                args.Camera.Position(args.Device);
                
                if (IsInitialized == false)
                {
                    Initialize(args);
                }

               


                for (int stage = 0; stage < NumStages; stage++)
                {
                    for (int part = 0; part < NumParts; part++)
                    {
                        DrawArgsDX innerArgs = new DrawArgsDX(args.Device, args.Camera, part, stage);
                        OnDraw(innerArgs);
                    }
                }
                OnDrawingCompleted(new DrawCompletedArgsDX(args, false, null));
            //}
            //catch (Exception ex)
            //{
            //    OnDrawingCompleted(this, new DrawCompletedArgsDX(args, false, ex));
            //}


        }
        



        /// <summary>
        /// Determines the geographic distance to use for something that is one pixel wide on the screen
        /// </summary>
        /// <param name="args">A DrawArgsDX with a Microsoft.DirectX.Device</param>
        /// <returns>A float multiplier for the scale</returns>
        public float GetScale(DrawArgsDX args)
        {
            Viewport vp = args.Device.Viewport;
            Transforms t = args.Device.Transform;
            // Find the point along the view path of the camera that intersects with the 0 elevation of the z plane
            //Vector3 pt = Plane.IntersectLine(args.Camera.XYPlane, args.Camera.CameraPosition, args.Camera.TargetPosition);
            Vector3 pt = Vector3.Subtract(args.Camera.TargetPosition, args.Camera.CameraPosition);
            float z = Math.Abs(args.Camera.CameraPosition.Z);
            pt.X = pt.X * z / pt.Z;
            pt.Y = pt.Y * z / pt.Z;
            pt = Vector3.Add(args.Camera.CameraPosition, pt);
            pt.Z = 0;

            // Project that into screen space in order to determine where the 0-elevation plane is between the near plane an the far plane of the scene
            Vector3 depth = Vector3.Project(pt, vp, t.Projection, t.View, t.World);

            // Back project two pixels from the screen into world coordinates that are separated by a distance of 1 pixel
            Vector3 center = new Vector3((float)vp.Width / 2, (float)vp.Height / 2, depth.Z);
            Vector3 right = new Vector3(vp.Width / 2 + 1, (float)vp.Height / 2, depth.Z);
            Vector3 wCenter = Vector3.Unproject(center, vp, args.Device.Transform.Projection, args.Device.Transform.View, args.Device.Transform.World);
            Vector3 wRight = Vector3.Unproject(right, vp, args.Device.Transform.Projection, args.Device.Transform.View, args.Device.Transform.World);
            
            // The distance between the two locations in world coordinates gives a scale value.
            Vector3 diff = Vector3.Subtract(wRight, wCenter);
           
            return diff.Length();
        }

       

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the draw box for this PointLayer
        /// </summary>
        [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public DrawBox DrawBox
        {
            get { return _drawBox; }
            set { _drawBox = value; }
        }

        /// <summary>
        /// Gets or sets a preference for how the symbol is drawn, though this may be temporary.
        /// </summary>
        public bool IsTextured
        {
            get { return _isTextured;  }
            set { _isTextured = value; }
        }

        /// <summary>
        /// Gets the number of parts in this drawing structure
        /// </summary>
        [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual int NumParts
        {
            get { return _numParts; }
            protected set { _numParts = value; }
        }

        /// <summary>
        /// Gets the number of stages that will be drawn.  Each stage cycles through every part.
        /// </summary>
        [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual int NumStages
        {
            get { return _numStages; }
            protected set { _numStages = value; }
        }

        /// <summary>
        /// This property is computed during re-scaling.  If the ScaleMode is set to Symbolic, this
        /// will represent the scale that was used when initializing the symbols.
        /// </summary>
        [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual float PreviousScale
        {
            get
            {
                return _previousScale;
            }
            set
            {
                _previousScale = value;
            }
        }



        #endregion

        #region Protected Methods


        /// <summary>
        /// This is overridden in order to add a check to see if we need to re-scale.  This allows us
        /// to do the re-scaling check one time, instead of repeating it during each part or stage.
        /// </summary>
        /// <param name="e">A DrawVerifyArgs</param>
        /// <returns></returns>
        protected virtual bool OnBeforeDrawing(DrawVerifyArgsDX e)
        {
            float scale = GetScale(e);

            if (Symbolizer.ScaleMode == ScaleModes.Symbolic)
            {

                if (scale < _previousScale * .8 || scale > _previousScale * 1.25)
                {
                    // Zoom has changed enough to invalidate this.  Since OnBeforeDrawing is checked before Initialization, 
                    // this should invalidate the drawing when we need it.
                    base.IsInitialized = false;
                    _previousScale = scale;
                }
            }
            if (BeforeDrawing == null) return false;
            BeforeDrawing(this, e);
            return e.Cancel;
        }

       
        /// <summary>
        /// Fires an event when we are done with the drawing code, whether the drawing was successful, cancelled, or threw an exception.
        /// </summary>
        /// <param name="e">A DrawCompletedArgsDX parameter containing information about the drawing</param>
        protected virtual void OnDrawingCompleted(DrawCompletedArgsDX e)
        {
            if (DrawingCompleted == null) return;
            DrawingCompleted(this, e);
        }

        /// <summary>
        /// Ensures that if the mode is symbolic, the initialization code will occur every time.
        /// </summary>
        /// <param name="e"></param>
        protected virtual void OnDrawSetup(DrawSetupArgs e)
        {
            if (_drawBox == null || e.Camera.DrawBox.Equals(_drawBox) == false)
            {
                IsInitialized = false;
                _drawBox = e.Camera.DrawBox;
            }

            if (Symbolizer.ScaleMode == ScaleModes.Symbolic)
            {
                double z = e.Camera.CameraLocation.Z;
                if (z > _previousZ * 2 || z < _previousZ / 2)
                {
                    base.IsInitialized = false;
                    _previousZ = z;
                }

            }
        }

       


        /// <summary>
        /// Fires the Initialized event
        /// </summary>
        /// <param name="e">An EventArgs parameter</param>
        protected virtual void OnInitialize(DrawArgsDX e)
        {
            IsInitialized = true;
            if (Inititialized != null)
            {
                Inititialized(this, e);
            }
            
        }

       

        #endregion

        #endregion


     


        

    }
}
